/**
 * Copyright (c) 2009 Cerner Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Cerner Corporation - initial API and implementation
 */
package com.cerner.system.rest.resource.organizer.spi;

import com.cerner.system.rest.resource.organizer.ResourceOrganizer;

/**
 * Thread-safe helper class that uniquely identifies {@link ResourceOrganizer}
 * objects in a hierarchical tree structure.
 * 
 * @author Alex Horn
 */
@ThreadSafe
public final class ResourceKey {

  /**
   * Absolute URI for a node in the hierarchical tree structure. It is never
   * {@code null}.
   */
  private final java.net.URI uri;

  /**
   * The number of slashes in the normalized {@link java.net.URI#getPath() URI
   * path} indicate how many parents there are. It is zero if the current key's
   * URI path is the root or if the {@link #uri} is opaque.
   */
  private final int level;

  /**
   * Constructor to instantiate a new {@link ResourceKey} that uniquely
   * identifies {@link ResourceOrganizer} objects in a hierarchical tree. The
   * URI as part of the second constructor argument will first be
   * {@link java.net.URI#resolve(String) resolved} against the {@code baseURI}.
   * <p>
   * The base URI must not be {@code null}. In addition, the base URI must
   * conform to the <a
   * href="http://tools.ietf.org/html/rfc3986#section-4.3">&lt;absolute-URI&gt;
   * syntax rule</a>.
   * 
   * @param baseURI base uniform resource identifier
   * @param uri uniform resource identifier for which the
   *          {@link ResourceOrganizer} is responsible
   */
  public ResourceKey(final java.net.URI baseURI, final java.net.URI uri) {
    this(baseURI.resolve(uri));
  }

  /**
   * Strictly internal constructor to instantiate a new {@link ResourceKey} with
   * an absolute URI.
   * 
   * @param uri normalized absolute URI
   */
  private ResourceKey(final java.net.URI uri) {
    this(uri,
        countSlashes(uri.getPath() /* path is never null for base URIs */));
  }

  /**
   * Strictly internal constructor to instantiate a new {@link ResourceKey} with
   * an absolute URI and a known number of parents.
   * 
   * @param uri normalized absolute URI
   * @param level number of slashes in the normalized
   *          {@link java.net.URI#getPath() URI path}
   */
  private ResourceKey(final java.net.URI uri, final int level) {
    // sanity (see parent() logic)
    assert level >= 0;

    this.uri = uri;
    this.level = level;
  }

  /**
   * Obtain the normalized absolute uniform resource identifier.
   * 
   * @return normalized absolute URI
   */
  public java.net.URI uri() {
    return this.uri;
  }

  /**
   * Obtain the {@link ResourceKey} that identifies the direct parent of the
   * current key. The return value is {@code null} if this key does not have a
   * parent.
   * 
   * @return parent key or {@code null} if there is none
   */
  public ResourceKey parent() {
    if (this.level == 0) {
      return null;
    } else if (uri().getPath().endsWith("/")) {
      /* level >= 1 */
      return new ResourceKey(uri().resolve("../"), this.level - 1);
    } else {
      /* level >= 1 */
      return new ResourceKey(uri().resolve("./"), this.level - 1);
    }
  }

  /**
   * Determine the number of times {@link #parent()} can be called without
   * returning a {@code null} value. The return value is always a non-negative
   * number.
   * 
   * @return number of parents
   */
  public int level() {
    return this.level;
  }

  /**
   * Compare two {@link ResourceKey} objects for equality. They are equal if
   * their URIs are {@link java.net.URI#equals(Object) equal}.
   * 
   * @return boolean {@code true} if both key URIs match, {@code false}
   *         otherwise
   */
  @Override
  public boolean equals(final Object object) {
    if (this == object) {
      return true;
    } else if (object instanceof ResourceKey) {
      final ResourceKey other = (ResourceKey) (object);

      /* other is not null */
      return uri().equals(other.uri());
    } else {
      return false;
    }
  }

  /**
   * Compute the hash code from resource URI.
   * 
   * @return hash code of this key
   */
  @Override
  public int hashCode() {
    return this.uri.hashCode();
  }

  /**
   * Obtain a debug-friendly representation of this {@link ResourceKey}.
   */
  @Override
  public String toString() {
    return this.uri.toString();
  }

  /**
   * Strictly internal API to count the number of non-trailing slashes in the
   * provided URI path segment.
   * 
   * @param path non-{@code null} URI path segment
   * @return number of non-trailing slashes in URI path
   */
  static int countSlashes(final String path) {
    final char[] chars = path.toCharArray();

    /* count all delimiting slashes expect the trailing slash (if any) */
    int slashes = 0;
    for (int i = 0; i < chars.length - 1; i++) {
      if (chars[i] == '/') {
        slashes++;
      }
    }

    return slashes;
  }

}
